package com.wfg.nettydemo.io.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * @author wufagang
 * @description
 * @date 2022年05月28日 18:43
 */
public class NIOServer2 {

    public static void main(String[] args) throws IOException {
        //创建ServerSocketChannel -> ServerSocket
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(7000));
        //设置为非阻塞，如果为true 退化成BIO
        serverSocketChannel.configureBlocking(false);
        //得到一个Selecor对象
        //linux 多路复用器（select poll epoll kqueue） nginx event
        //如果在epoll 模型下，open --》epoll create --》 fd3
        //select poll poll 优先选择 epoll 但是可以-d 修正
        Selector selector = Selector.open();

        //serverSocketChannel 约等于 listen状态的 fd4
        // register
        // 如果 select poll jvm 里开辟了一个数组 fd4 放进去
        //epoll ： epoll_ctl(fd3, add , fd4, epollin)
        //把 serverSocketChannel 注册到  selector 关心 事件为 OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("注册后的selectionkey 数量=" + selector.keys().size()); // 1
        //循环等待客户端连接
        while (true){
            if(selector.select(10000) == 0){
                System.out.println("服务器等待十秒，无链接");
                continue;
            }
            //如果返回的>0, 就获取到相关的 selectionKey集合
            //1.如果返回的>0， 表示已经获取到关注的事件
            //2. selector.selectedKeys() 返回关注事件的集合
            //   通过 selectionKeys 反向获取通道
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            System.out.println("selectionKeys 数量 = " + selectionKeys.size());
            //1. 调用多路复用器（select poll or epoll （epoll——wait））
            /**select（） 啥意思：
             * 1.select poll 其实 内核的select （fd4） poll（fd4）
             * 2. epoll 其实内核的epoll_wait()
             * 参数可以带时间： 没有时间 0 阻塞，有时间设置一个超时
             * slector.wakeup() 结果返回0
            */
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                if(key.isAcceptable()){ //有新到客户端链接
                    /**
                     *看代码对时候，这里是重点，如果要去接受一个新对连接
                     * 语义上，accept接受连接且返回新连接对fd
                     * 那新对fd怎么办？
                     * select poll 因为他们内核没有空间，那么在jvm中保存和前边对fd4那个listen的一起
                     * epoll： 我们希望通过epoll_ctl把新的客户端fd注册到内核空间
                     */
                    //给该客户端生成一个socketchannel
                    //accept 方法本身是阻塞的，但是到这已经事件发生了 所以不会阻塞
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    //将socketchannel 注册到selector 关注事件 为read 同时给socketchannel 关联一个buffer
                    socketChannel.configureBlocking(false);
                    //select poll : jvm 里开辟一个数组 fd7 放进去
                    //epoll ：相当于 epoll_ctl(fd3,add,fd7,epollin)
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));

                }else  if(key.isReadable()){ //发生read 事件
                    //通过key 反向获取channel
                    SocketChannel socketChannel = (SocketChannel)key.channel();
                    //获取该channel关联到buffer
                    ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
                    int read = socketChannel.read(byteBuffer);
                    System.out.println("从客户端：" + new String(byteBuffer.array(),0,read));

                }
                //手动从集合中移动当前的selectionKey, 防止重复操作
                iterator.remove();
            }


        }


    }
}
